package es.ucm.fdi.xtremeaudio.modelo.parser;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

import javax.swing.JOptionPane;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

import es.ucm.fdi.xtremeaudio.modelo.beans.Biblioteca;
import es.ucm.fdi.xtremeaudio.modelo.beans.Cancion;
import es.ucm.fdi.xtremeaudio.modelo.beans.Playlist;
import es.ucm.fdi.xtremeaudio.modelo.excepciones.ReaderException;

/**
 * <ul>
 * <li><b>Proyecto:</b> XtremeAudio</li>
 * <li><b>Grupo:</b> 7 <i>(4&deg;B)</i></li>
 * <li><b>Asignatura:</b> Ingenier&iacute;a del Software</li>
 * <li><b>Curso:</b> 2010/2011</li>
 * <li><b>Centro:</b> Universidad Complutense de Madrid</li>
 * </ul>
 * 
 * La clase <b>ParserSAXBiblio</b> se encarga de parsear el archivo <i>XML</i>
 * asociado a la biblioteca, de manera que si el archivo existe y es correcto
 * (respecto a la especifica&oacute;n indicada en el archivo
 * <i>biblioteca.dtd</i>, en la carpeta &quot;conf&quot;), devolver&aacute; el
 * mapeado de dicho archivo en forma de objeto de la clase {@link Biblioteca}.
 * En la primera ejecuci&oacute;n de la aplicaci&oacute;n, dicha biblioteca
 * estar&aacute; registrada en el archivo <i>XML</i>, bien formado y
 * v&aacute;lido, como una biblioteca vac&iacute;a. Sucesivas ejecuciones
 * (habiendo guardado la biblioteca importada alguna vez), usar&aacute;n esta
 * clase al inicio de las mismas para cargar la biblioteca almacenada
 * directamente, sin tomar parte el usuario. Esta clase es una extensi&oacute;n
 * de la clase {@link DefaultHandler}.
 * <p>
 * <b><i>NOTA:</i></b> En caso de el archivo <u>no exista</u> (por descuido o
 * torpeza), al iniciar la aplicaci&oacute;n se crear&aacute; uno por defecto
 * (biblioteca vac&iacute;a).
 * </p>
 * 
 * @author &Aacute;lvaro G&oacute;mez Porris
 * @author Elena Hern&aacute;ndez Delgado
 * @author Mar&iacute;a Trinidad Mart&iacute;n Campos
 * @author Mois&eacute;s Mart&iacute;nez Montes
 * @author Mat&iacute;as Salinero Delgado
 * 
 * @version %I%, %G%
 * 
 * @since 1.0
 * 
 * @see Biblioteca
 * @see Cancion
 */
public class ParserSAXBiblio extends DefaultHandler {

	/*
	 * Constantes para identificar para elemento
	 */
	private static final int C_CANCION = 0;
	private static final int C_AUTOR = 1;
	private static final int C_ALBUM = 2;
	private static final int C_TITULO = 3;
	private static final int C_GENERO = 4;
	private static final int C_ANYO = 5;
	private static final int C_PISTA = 6;
	private static final int C_DURACION = 7;
	private static final int C_FORMATO = 8;
	private static final int C_PATH = 9;
	private static final int C_PLAYLIST = 10;
	private static final int C_NOMBRE = 11;
	private static final int C_RUTA = 12;

	/*
	 * Tenemos un par de arrays para saber Dentro de que elemento estamos en un
	 * momento dado
	 */
	private String[] sListaElementos = { "cancion", "autor", "album", "titulo",
			"genero", "anyo", "pista", "duracion", "formato", "path",
			"playlist", "nombre", "ruta" };
	private int iTotalElementos = sListaElementos.length;
	private boolean[] bDentro = new boolean[iTotalElementos];

	/*
	 * Lista de canciones resultado de un parseo
	 */
	private ArrayList<Cancion> vCanciones = null;

	/*
	 * Cancion actualmente leido en un proceso de parseo
	 */
	private Cancion cCancionActual = null;

	/*
	 * Lista de Playlists resultado de un parseo
	 */
	private ArrayList<Playlist> vPlaylists = null;

	/*
	 * Playlist actualmente leida en un proceso de parseo
	 */
	private Playlist pPlaylistActual = null;

	/*
	 * Buffer de caracteres de un elemento en un proceso de parseo
	 */
	private StringBuffer sBuffer = null;

	/**
	 * Construtora por defecto.
	 */
	public ParserSAXBiblio() {
		for (int i = 0; i < iTotalElementos; i++) {
			bDentro[i] = false;
		}
	}

	/**
	 * Se encarga de crear el parser, el cual validar&aacute; el archivo
	 * indicado y lo procesar&aacute; mediante el mecanismo <i>SAX</i> de parseo
	 * de archivos <i>XML</i>. El parseo consiste en mapear las etiquetas del
	 * archivo a un objeto de tipo biblioteca, que servir&aacute; de base para
	 * la ejecuci&oacute;n de la aplicaci&oacute;n.
	 * 
	 * @param filename
	 *            La ruta del archivo a parsear.
	 * 
	 * @return La biblioteca.
	 * 
	 * @throws ReaderException
	 *             Aglutina una serie de excepciones relacionadas con la
	 *             apertura del archivo y el parseo en s&iacute;.
	 * 
	 * @see Biblioteca
	 * @see SAXParser
	 * @see SAXParserFactory
	 */
	public Biblioteca vParseFile(String filename) throws ReaderException {
		vCanciones = new ArrayList<Cancion>();
		vPlaylists = new ArrayList<Playlist>();
		try {
			// Obtener una clase SAXParserFactory para obtener un SAXParser
			SAXParserFactory oSAXParserFactory = SAXParserFactory.newInstance();

			// Definimos las opciones de parseo: validar el xml
			oSAXParserFactory.setValidating(false);

			// Definimos las opciones de parseo: namespace no activado
			oSAXParserFactory.setNamespaceAware(false);

			// Obtener una clase SAXParser
			SAXParser oSAXParser = oSAXParserFactory.newSAXParser();

			// Empezar a parsear el archivo con esta clase para recoger los
			// eventos de parseo
			oSAXParser.parse(new File(filename), this);

		} catch (FactoryConfigurationError e) {
			throw new ReaderException(
					"ReaderException[FactoryConfigurationError]: "
							+ e.toString());
		} catch (ParserConfigurationException e) {
			throw new ReaderException(
					"ReaderException[ParserConfigurationException]: "
							+ e.toString());
		} catch (SAXException e) {
			throw new ReaderException("ReaderException[SAXException]: "
					+ e.toString());
		} catch (IOException e) {
			throw new ReaderException("ReaderException[IOException]: "
					+ e.toString());
		}
		Biblioteca biblioteca = new Biblioteca(vCanciones, vPlaylists, 0, -1);
		return biblioteca;
	}

	/**
	 * <u>M&eacute;todo sobreescrito</u>. Muestra por consola el resultado de un
	 * warning en el parseo.
	 * 
	 * @see org.xml.sax.helpers.DefaultHandler#warning(org.xml.sax.SAXParseException)
	 */
	@Override
	public void warning(SAXParseException e) throws SAXException {
		System.out.println("Warning: ");
		printInfo(e);
	}

	/**
	 * <u>M&eacute;todo sobreescrito</u>. Muestra por consola el resultado de un
	 * error en el parseo.
	 * 
	 * @see org.xml.sax.helpers.DefaultHandler#error(org.xml.sax.SAXParseException)
	 */
	@Override
	public void error(SAXParseException e) throws SAXException {
		System.out.println("Error: ");
		printInfo(e);
	}

	/**
	 * <u>M&eacute;todo sobreescrito</u>. Muestra por consola el resultado de un
	 * error fatal en el parseo.
	 * 
	 * @see org.xml.sax.helpers.DefaultHandler#fatalError(org.xml.sax.SAXParseException)
	 */
	@Override
	public void fatalError(SAXParseException e) throws SAXException {
		System.out.println("Error Fatal: ");
		printInfo(e);
	}

	/**
	 * <u>M&eacute;todo sobreescrito</u>. Realiza las acciones pertinentes al
	 * encontrar una etiqueta de apertura. En este caso, crea una nueva
	 * canci&oacute;n o lista de reproducci&oacute;n en funci&oacute;n del tipo
	 * de etiqueta parseada.
	 * 
	 * @see Cancion
	 * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String,
	 *      java.lang.String, java.lang.String, org.xml.sax.Attributes)
	 * @see Playlist
	 */
	@Override
	public void startElement(String namespaceURI, String localName,
			String qualifiedName, Attributes attributes) throws SAXException {
		
		// Si localName es vacio entonces el nombre del
		// elemento esta en qualifiedName
		if ("".equals(localName))
			localName = qualifiedName;

		// averiguar Dentro de que elemento estamos
		for (int i = 0; i < iTotalElementos; i++) {
			if (localName.compareToIgnoreCase(sListaElementos[i]) == 0) {
				bDentro[i] = true;
				break;
			}
		}

		// Si el elemento es una cancion o una lista,
		// parsear el atributo (id)
		if (!(localName.equalsIgnoreCase("biblioteca"))) {
			if ((localName.equals(sListaElementos[C_CANCION]))
					|| (localName.equals(sListaElementos[C_PLAYLIST]))) {
				System.out.print("  ");
			} else {
				System.out.print("     ");
			}
		}
		System.out.print("<" + localName);
		String id;
		if (localName
				.compareToIgnoreCase(sListaElementos[ParserSAXBiblio.C_CANCION]) == 0) {
			cCancionActual = new Cancion();
			id = parseaId(attributes);
			System.out.print(" id=\"" + id + "\"");
			cCancionActual.setId(Integer.valueOf(id));
		}

		// Si el elemento es una playlist, se crea
		if (localName
				.compareToIgnoreCase(sListaElementos[ParserSAXBiblio.C_PLAYLIST]) == 0) {
			pPlaylistActual = new Playlist();
			id = parseaId(attributes);
			System.out.print(" id=\"" + id + "\"");
			pPlaylistActual.setId(Integer.valueOf(id));
		}

		System.out.println(">");
	}

	/**
	 * <u>M&eacute;todo sobreescrito</u>. Procesa la cadena de caracteres
	 * situada entre dos etiquetas del mismo tipo.
	 * 
	 * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int)
	 */
	@Override
	public void characters(char buf[], int offset, int len) throws SAXException {
		String s = new String(buf, offset, len);
		if (sBuffer == null)
			sBuffer = new StringBuffer(len);
		sBuffer.append(s.trim());
		System.out.println("\t" + sBuffer);
	}

	/**
	 * <u>M&eacute;todo sobreescrito</u>. Realiza las acciones pertinentes al
	 * encontrar una etiqueta de cierre. En este caso, almacena la cadena
	 * obtenida en {@link #characters(char[], int, int)} en el atributo adecuado
	 * de una {@link Cancion} (si el cierre era de una canci&oacute;n), de una
	 * {@link Playlist} (si era de &eacute;sta) y a&ntilde;ade el objeto
	 * adecuado a alguno de los vectores de la biblioteca, seg&uacute;n
	 * corresponda.
	 * 
	 * @see Biblioteca
	 * @see Cancion
	 * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String,
	 *      java.lang.String, java.lang.String)
	 * @see Playlist
	 */
	@Override
	public void endElement(String uri, String localName, String qualifiedName)
			throws SAXException {

		if ("".equals(localName))
			localName = qualifiedName;

		String s = null;
		if (sBuffer != null) {
			s = sBuffer.toString();
		}

		// asignar contenido
		// si es un AUTOR establecer el contenido en la cancion actual
		if (bDentro[ParserSAXBiblio.C_AUTOR])
			cCancionActual.setAutor(s);

		// si es un ALBUM establecer el contenido en la cancion actual
		if (bDentro[ParserSAXBiblio.C_ALBUM])
			cCancionActual.setAlbum(s);

		// si es un TITULO establecer el contenido en la cancion actual
		if (bDentro[ParserSAXBiblio.C_TITULO])
			cCancionActual.setTitulo(s);

		// si es un GENERO establecer el contenido en la cancion actual
		if (bDentro[ParserSAXBiblio.C_GENERO])
			cCancionActual.setGenero(s);

		// si es un ANYO establecer el contenido en la cancion actual
		if (bDentro[ParserSAXBiblio.C_ANYO])
			cCancionActual.setAnyo(s);

		// si es una PISTA establecer el contenido en la cancion actual
		if (bDentro[ParserSAXBiblio.C_PISTA])
			cCancionActual.setPista(s);

		// si es una DURACION establecer el contenido en la cancion actual
		if (bDentro[ParserSAXBiblio.C_DURACION]) {
			String[] dur = s.split(":");
			int d = (Integer.valueOf(dur[0]) * 60) + Integer.valueOf(dur[1]);
			cCancionActual.setDuracion(d);
			cCancionActual.setStringDuracion(s);
		}

		// si es un FORMATO establecer el contenido en la cancion actual
		if (bDentro[ParserSAXBiblio.C_FORMATO])
			cCancionActual.setFormato(s);

		// si es un PATH establecer el contenido en la cancion actual
		if (bDentro[ParserSAXBiblio.C_PATH])
			cCancionActual.setPath(s);

		// si es un NOMBRE establecer el contenido en la playlist actual
		if (bDentro[ParserSAXBiblio.C_NOMBRE])
			pPlaylistActual.setNombre(s);

		// si es una RUTA establecer el contenido en la playlist actual
		if (bDentro[ParserSAXBiblio.C_RUTA])
			pPlaylistActual.setRuta(s);

		if (localName
				.compareToIgnoreCase(sListaElementos[ParserSAXBiblio.C_CANCION]) == 0) {
			// si es una cancion la incluimos en la lista de canciones
			vCanciones.add(cCancionActual);
		}

		if (localName
				.compareToIgnoreCase(sListaElementos[ParserSAXBiblio.C_PLAYLIST]) == 0) {

			// si es una cancion la incluimos en la lista de playlists
			vPlaylists.add(pPlaylistActual);
		}

		if (!(localName.equalsIgnoreCase("biblioteca"))) {
			if ((localName.equals(sListaElementos[C_CANCION]))
					|| (localName.equals(sListaElementos[C_PLAYLIST]))) {
				System.out.print("  ");
			} else {
				System.out.print("     ");
			}
		}
		System.out.println("</" + localName + ">");
		if (localName.equalsIgnoreCase("biblioteca")) {
			System.out.println();
		}

		// Limpiamos sBuffer
		sBuffer = null;

		// Actualizar array bDentro
		for (int i = 0; i < iTotalElementos; i++) {
			if (localName.compareToIgnoreCase(sListaElementos[i]) == 0) {
				bDentro[i] = false;
				break;
			}
		}
	}

	/*--- METODOS PRIVADOS ---*/
	private String parseaId(Attributes attributes) {
		String id = null;
		for (int i = 0; i < attributes.getLength(); i++) {
			if ((attributes.getLocalName(i).compareToIgnoreCase("id") == 0)
					|| (attributes.getQName(i).compareToIgnoreCase("id") == 0)) {
				id = attributes.getValue(i);
				break;
			}
		}
		return id;
	}

	private void printInfo(SAXParseException e) {
		System.out.println("\tPublic ID: " + e.getPublicId());
		System.out.println("\tSystem ID: " + e.getSystemId());
		System.out.println("\tN\u00famero de l\u00ednea: " + e.getLineNumber());
		System.out.println("\tN\u00famero de columna: " + e.getColumnNumber());
		System.out.println("\tMensaje: " + e.getMessage() + "\n");
	}

}